package Logica;

import RMI.RemoteClientInterface;
import java.io.*;
import java.net.*;
import java.nio.file.Path;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Servidor extends Thread {

    public static RemoteClientService service;
    private static int TIMEOUT = 10 * 1000;
    private static int serverPort = 6001;
    private static int serverPort2 = 7000;
    private static int udpPort = 56000;
    private static String loginFilePath;//= "C:\\Users\\NuNo\\Desktop\\3º ano\\PD\\TP\\users.txt";
    //private static String loginFilePath = "C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\users.txt";
    private static ServerSocket ss;
    private static List<Socket> cs;
    private static List<Socket> refreshSocket;
    private static ServerSocket refreshServerSocket;
    private static List<ObjectOutputStream> outRefresh;
    private static List<Boolean> connectado;
    //Variaveis Repositorios
    private static int udpPort2 = 60000;
    private static int udpPort3 = 61000;
    private static DatagramSocket dss;
    private static DatagramPacket dsp;
    private static DatagramSocket dss2;
    private static DatagramPacket dsp2;
    //private static List<DatagramSocket> dsocket;
    private static List<DatagramPacket> dpacket;
    private static List<Integer> timer = new ArrayList<Integer>();
    private static List<InetAddress> listaAddrsReps; 
    private static List<Integer> listaPortReps;
    private static List<Integer> cargaRepositorios;
    private static List<Boolean> connectadoRep = new ArrayList<Boolean> ();
    static int idRepositorio = 0;
    
    //private static int [] dp;
    static int[] atividade; // 1º id num repositorio 2º id num ficheiro
    //fim
    static File pasta;
    static List<Ficheiro> file;
    static GereRepositorio t2;
    static InicializaRepositorios t4;
    private BufferedReader recebe;
    private static List<String> listaConectados;
    private static List<String> ultimaOperacao;
    static int id;
    static int num = 0;
    static int NumeroFicheiros = 0;
    static GuardaSocketFicheirosClientes t3;
    boolean utilizadorComMesmoNomeConectado = false;
   
    public List<Ficheiro> getFile() {
        return file;
    }

    public void setFile(List<Ficheiro> file) {
        this.file = file;
    }

    
    private static class RemoteClientService extends UnicastRemoteObject implements RemoteClientInterface 
    {

        RemoteClientService() throws RemoteException 
        {
        }

        public String getListaNomesConectados() throws RemoteException 
        {
            int i=0;
            String str="";
            for( String s : listaConectados)
            {
                if( connectado.get(i) )
                    str += s+"\t";           
                i++;
            }
            return str;
        }

        public String getTipoOperacaoEmCurso() throws RemoteException 
        {
            String str="";
            for( String s : ultimaOperacao)
                str += s+"\t";
            return str;
        }

        public String getListaFicheiros() throws RemoteException 
        {
            return toStringListaFicheiros();
        }

        public String getAddrRepositoriosAtivos() throws RemoteException 
        {
            String str="";
            String s="";
            for( DatagramPacket p : dpacket)
            {
                s = p.getAddress().toString();
                str += s+"\t";
            }
            return str;
        }

        public String getPortoRepositoriosAtivos() throws RemoteException 
        {
            String str="";
            String s="";
            for( DatagramPacket p : dpacket)
            {
                s = String.valueOf(p.getPort());
                str += s+"\t";
            }
            return str;
        }

        // Lanca e regista um servico do tipo RemoteTimeServico sob o nome RemoteTime
        public void iniciaServicoRMI() 
        {
            try {
                Registry r;

                try {
                    System.out.println("Tentativa de lancamento do registry no porto "+ Registry.REGISTRY_PORT + "...");
                    r = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
                    System.out.println("Registry lancado!");

                } catch (RemoteException e) {
                    System.out.println("Registry provavelmente ja' em execucao!");
                    r = LocateRegistry.getRegistry();
                }

            // Cria e lanca o servico,
                RemoteClientService clienteService = new RemoteClientService();

                System.out.println("Servico RemoteBox criado e em execucao (" + clienteService.getRef().remoteToString() + "...");

            // Regista o servico para que os clientes possam encontra'-lo, ou seja,
                // obter a sua referencia remota (endereco IP, porto de escuta, etc.).
                r.rebind("RemoteBox", clienteService);

                System.out.println("Servico RemoteTime registado no registry...");

            } catch (RemoteException e) {
                System.out.println("Erro remoto - " + e);
                System.exit(1);
            } catch (Exception e) {
                System.out.println("Erro - " + e);
                System.exit(1);
            }
        }
    }

   
    private static class VerificaAtividadeRepositorios extends Thread
    {
        public void run()
        {
            while(true)
            {
                if( idRepositorio >= 1 )
                {
                    try 
                    {
                        for (int i = 0; i < dpacket.size(); i++) 
                        {
                            timer.set(i, (timer.get(i)+1));
                            if( timer.get(i) >= 30 )
                            {
                                connectadoRep.set(i, false);
                                System.out.println("repositorio "+i+" desconetou");
                            }
                        }
                        Thread.sleep(1000);
                        
                    } 
                    catch (InterruptedException ex) 
                    {
                        Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
    }
    
    private static class GuardaSocketFicheirosClientes extends Thread {

        public void run() {
            try {
                while (true) {
                    refreshSocket.add(refreshServerSocket.accept());
                    outRefresh.add(new ObjectOutputStream(refreshSocket.get(num).getOutputStream()));
                    num++;
                }
            } catch (IOException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public static class InicializaRepositorios extends Thread {

        public InicializaRepositorios() {
            this.start();
        }
        ByteArrayOutputStream bout;
        ObjectOutputStream out;

        public void run() {
            try {
                dss2 = new DatagramSocket(udpPort2);
            } catch (SocketException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
            dsp2 = new DatagramPacket(new byte[256], 256);

            while (true) {
                try {
                    dss2.receive(dsp2);
                    System.out.println("Recebi");
                    System.out.println("port:  " + dsp2.getPort());
                    DatagramPacket novoDataPack = new DatagramPacket(dsp2.getData(), dsp2.getLength(), dsp2.getAddress(), dsp2.getPort());
                    dpacket.add(novoDataPack);

                    //ds.add(dsp2.getAddress());
                    //dp[idRepositorio] = dsp2.getPort();
                    bout = new ByteArrayOutputStream();
                    out = new ObjectOutputStream(bout);
                    
                    MensagemRepositorio mensagem = new MensagemRepositorio(file,idRepositorio,listaAddrsReps, listaPortReps);
                    
                    out.writeObject(mensagem);
                    out.flush();

                    dsp2.setData(bout.toByteArray());
                    dsp2.setLength(bout.size());

                    dss2.send(dsp2);
                    System.out.println("Enviei lista de ficheiros");
                    
                    listaAddrsReps.add( dsp2.getAddress() );      //guarda address
                    listaPortReps.add(10000 + idRepositorio);     //guarda porto
                    
                    timer.add(0);
                    connectadoRep.add(true);
                    
                    idRepositorio++;
                } catch (IOException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    public static void processaMensagem(Mensagem sms) {
        int i = 0;
        if (sms.atualizado) {
            file = sms.file;
            System.out.println(toStringListaFicheiros());
            for (Socket sock : refreshSocket) {

                try {
                    if (connectado.get(i)) {
                        outRefresh.get(i).writeObject(file);
                        outRefresh.get(i).flush();
                        outRefresh.get(i).reset();
                    }
                    i++;
                } catch (IOException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        System.out.println("Tamanho HeartBeat Repositorio " + sms.id + ": " + sms.sms);
        if(!timer.isEmpty())
            timer.set(sms.id,0);
        if(!cargaRepositorios.isEmpty())
            cargaRepositorios.set(sms.id, Integer.parseInt(sms.sms));
    }

    public static class GereRepositorio extends Thread {
        VerificaAtividadeRepositorios resetTimer;
        GereRepositorio() {
            resetTimer = new VerificaAtividadeRepositorios();
            try {
                dss = new DatagramSocket(udpPort);
            } catch (SocketException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
            dsp = new DatagramPacket(new byte[10000000], 10000000);
            this.start();
        }

        public void run() {
            resetTimer.start();
            while (true) {
                try {
                    dss.receive(dsp);

                    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(dsp.getData(), 0, dsp.getLength()));
                    Mensagem sms = (Mensagem) in.readObject();

                    processaMensagem(sms);

                } catch (IOException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                } catch (ClassNotFoundException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }

        
    }

    public void EnviaPedidoRepositorio(int flag, String nomeFicheiro, int idCliente, int idRep) {
        try {
            DatagramSocket dataSock = new DatagramSocket();
            DatagramPacket dataPack = new DatagramPacket(new byte[256], 256, dpacket.get(idRep).getAddress(), dpacket.get(idRep).getPort());
            switch (flag) {
                case 1:
                    byte[] data1 = "1".getBytes();
                    dataPack.setData(data1);
                    dataPack.setLength(data1.length);
                    dataSock.send(dataPack);
                    data1 = nomeFicheiro.getBytes();
                    dataPack.setData(data1);
                    dataPack.setLength(data1.length);
                    dataSock.send(dataPack);
                    break;
                case 2:
                    byte[] data2 = "2".getBytes();
                    dataPack.setData(data2);
                    dataPack.setLength(data2.length);
                    dataSock.send(dataPack);
                    data2 = nomeFicheiro.getBytes();
                    dataPack.setData(data2);
                    dataPack.setLength(data2.length);
                    dataSock.send(dataPack);
                    break;
                case 3:
                    byte[] data3 = "3".getBytes();
                    dataPack.setData(data3);
                    dataPack.setLength(data3.length);
                    dataSock.send(dataPack);
                    data3 = nomeFicheiro.getBytes();
                    dataPack.setData(data3);
                    dataPack.setLength(data3.length);
                    dataSock.send(dataPack);
                    break;
            }
        } catch (SocketException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    @Override
    public void run() 
    {
        PrintStream envia = null;
        BufferedReader recebe = null;
        int flag;
        int thisId = id;
        id++;

        try {
            envia = new PrintStream(cs.get(thisId).getOutputStream());
            if (!logIN(thisId)) 
            {
                if( utilizadorComMesmoNomeConectado )
                {
                    envia.println("3");
                    System.out.println("utilizadorComMesmoNomeConectado == true");
                    cs.remove(thisId);
                }
                else
                {
                    System.out.println("dados errados!");
                    envia.println("0");
                    cs.remove(thisId);
                }
                
                connectado.set(thisId, false);
                
                throw new SocketException();
            } 
            else 
            {
                ultimaOperacao.add("logado");
                System.out.println("Estas logado!");
                envia.println("1");
                
            }

        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }

        try {
            //Mostra lista ficheiros ao cliente iniciar sessão
            //Thread.sleep(500);

            outRefresh.get(thisId).writeObject(file);
            outRefresh.get(thisId).flush();
            outRefresh.get(thisId).reset();

        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }
            //

        try {
            recebe = new BufferedReader(new InputStreamReader(cs.get(thisId).getInputStream()));

            while (true) {
                String nomeFicheiro;
                int index;
                index = EscolheRepositorioMenosAtivo();
                try {
                    String aux = recebe.readLine();
                    flag = Integer.parseInt(aux);
                    switch (flag) {
                        case 1: // download
                            ultimaOperacao.set(thisId, "Download");
                            nomeFicheiro = recebe.readLine();
                            envia.println("127.0.0.1");
                            envia.println("9001");
                            EnviaPedidoRepositorio(1, nomeFicheiro, thisId, index);
                            //RedirecionaClienteRepositorio();
                            break;
                        case 2: //upload
                            ultimaOperacao.set(thisId, "Upload");
                            nomeFicheiro = recebe.readLine();
                            System.out.println("pedido upload: " + nomeFicheiro);
                            envia.println("127.0.0.1");
                            envia.println("9001");
                            //VerificaExistenciaFicheiros();
                            //RedirecionaClienteRepositorio();
                            EnviaPedidoRepositorio(2, nomeFicheiro, thisId, index);
                            break;
                        case 3: //eliminar
                            ultimaOperacao.set(thisId, "Eliminar");
                            nomeFicheiro = recebe.readLine();
                            EnviaPedidoRepositorio(3, nomeFicheiro, thisId, index);
                            break;
                        case 4:
                            connectado.set(thisId, false);
                            // sair
                            return;
                    }
                } catch (SocketException ex) 
                {
                    System.out.println("O utilizador " + thisId + " desligou-se");
                    ultimaOperacao.remove(thisId);
                    listaConectados.remove(thisId);
                    connectado.set(thisId, false);
                    try {
                        cs.get(thisId).close();
                    } catch (IOException ex1) {
                        System.out.println("Cliente " + thisId + " nao chegou a abrir as streams da socket, ou não existente");
                    }
                    return;
                } catch (IOException ex) {
                    Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
                }

            }

        } catch (IOException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public int EscolheRepositorioMenosAtivo()
    {
        int index_menor = 0;
        int menor=1000000;
        for (int j = 0; j < cargaRepositorios.size() ; j++) {
         if(cargaRepositorios.get(j)<menor)
         {
             menor=cargaRepositorios.get(j);
             index_menor=j;
         }
        }
        
        return index_menor;
    }
    
    public boolean logIN(int id) {
        FileReader fr = null;
        BufferedReader le_fich = null;
        String s;

        try {
            // Pede nome
            recebe = new BufferedReader(new InputStreamReader(cs.get(id).getInputStream()));

            String nome_log = new String(recebe.readLine());

            fr = new FileReader(loginFilePath);
            le_fich = new BufferedReader(fr);
            s = le_fich.readLine(); // le a 1� linha

            while (s != null) {

                if (s.equals(nome_log)) 
                {
                    String []nome = s.split(" ");
                    if ( isAlreadyConected(nome[0]) == false )
                    {
                        listaConectados.add(nome[0]);
                        return true;
                    }
                    else
                    {
                        utilizadorComMesmoNomeConectado=true;
                        return false;
                    }
                }

                System.out.println(s);
                s = le_fich.readLine(); // proxima linnha

            }

            try {
                fr.close();
                le_fich.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        } catch (FileNotFoundException e) {
            System.out.println("ERRO: " + e);
        } catch (SocketException e) {

            System.out.println("A socket no cliente " + id + " foi terminada");
            try {
                cs.get(id).close();
                outRefresh.get(id).close();
            } catch (IOException ex) {
                System.out.println("Socket com valor null, linha 366/367");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return false;
    }

    public boolean isAlreadyConected(String str)
    {
        for( String s : listaConectados )
            if( s.equals(str) )
                return true;
        return false;
    }
    
    static void verificaFicheirosPasta(String path) {
        pasta = new File(path);
        File[] listOfFiles = pasta.listFiles();
        for (int i = 0; i < listOfFiles.length; i++) {
            if (listOfFiles[i].isDirectory()) {
                verificaFicheirosPasta(listOfFiles[i].getPath());
            } else {
                adicionaFicheiroLista(listOfFiles[i]);
            }
        }
    }

    static void adicionaFicheiroLista(File fich) {
        Ficheiro novo = new Ficheiro(fich, fich.getName(), fich.length(), new Date(fich.lastModified()));
        file.add(novo);
    }

    static String toStringListaFicheiros() {
        String Mensagem = "";
        for (Ficheiro f : file) {
            if (f.file.isFile()) {
                Mensagem += "File " + f.file.getName() + "Date: " + f.getDataCriacao() + " Tamanho: " + f.getSize() + "\n";
            } else if (f.file.isDirectory()) {
                Mensagem += "Directory " + f.file.getName() + "Date: " + f.getDataCriacao() + " Tamanho: " + f.getSize() + "\n";
            }
        }
        return Mensagem;
    }

    public static void main(String[] args) {

        /*
         Ao lan�ar um servidor, deve ser-lhe fornecido 
         o porto de escuta e o nome do ficheiro 
         onde se encontram as credenciais dos 
         utilizadores autorizados (esta � a �nica 
         informa��o persistente mantida pelo servidor);
         */
        /*
         if( args.length != 2 ){
         System.out.println("Erro de Sintaxe: <Servidor> <ServerPort> <fileName>");
         return;
         }*/
        
        
        serverPort = Integer.parseInt("6001");
            
        loginFilePath= "C:\\Users\\NuNo\\Desktop\\3º ano\\PD\\TP\\users.txt";
        //loginFilePath = System.getProperty("user.dir") + "\\users.txt";
        //loginFilePath = "C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\repositorio1";
        //serverPort = Integer.parseInt(args[0]);
        //loginFilePath = System.getProperty("user.dir")+args[1].trim();
        id = 0;
        try {
            
            //connectadoRep = new ArrayList<Boolean>();
            ultimaOperacao = new ArrayList<String>();
            listaConectados = new ArrayList<String>();
            listaAddrsReps = new ArrayList<InetAddress>();
            listaPortReps = new ArrayList<Integer>();
            t2 = new GereRepositorio();
            dpacket = new ArrayList<DatagramPacket>();
            t4 = new InicializaRepositorios();
            ss = new ServerSocket(serverPort);
            cs = new ArrayList<Socket>();
            file = new ArrayList<Ficheiro>();
            refreshSocket = new ArrayList<Socket>();
            refreshServerSocket = new ServerSocket(serverPort2);
            connectado = new ArrayList<Boolean>();
            cargaRepositorios = new ArrayList<Integer>();
            t3 = new GuardaSocketFicheirosClientes();
            t3.start();
            service = new RemoteClientService();
            service.iniciaServicoRMI();
            //verificaFicheirosPasta(System.getProperty("user.dir") + "\\repositorio1");
            verificaFicheirosPasta("C:\\Users\\NuNo\\Dropbox\\PD\\TP_pdist\\repositorio1");
            //verificaFicheirosPasta("C:\\Users\\Rui Margarido\\Dropbox\\PD\\TP_pdist\\repositorio1");
            outRefresh = new ArrayList<ObjectOutputStream>();
            System.out.println(toStringListaFicheiros());
            
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        //ss.setSoTimeout(TIMEOUT);

        while (true) {

            try {
               
                cs.add(ss.accept());
                System.out.println("vez:" + id);
            } catch (IOException e) {
                e.printStackTrace();
            }

            //cs.setSoTimeout(TIMEOUT);
            //Runnable run = new Servidor();
            connectado.add(true);
            Thread t = new Servidor();
            t.setDaemon(true);
            t.start();
        }

    }

}

// falta receber o datapacket UDP

